/*-----------------------------------------------------------------------------
		   Licensed to the Apache Software Foundation (ASF) under one
           or more contributor license agreements.  See the NOTICE file
           distributed with this work for additional information
           regarding copyright ownership.  The ASF licenses this file
           to you under the Apache License, Version 2.0 (the
           "License"); you may not use this file except in compliance
           with the License.  You may obtain a copy of the License at

             http://www.apache.org/licenses/LICENSE-2.0

           Unless required by applicable law or agreed to in writing,
           software distributed under the License is distributed on an
           "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
           KIND, either express or implied.  See the License for the
           specific language governing permissions and limitations
           under the License.  
-----------------------------------------------------------------------------*/

package parser;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.Scanner;

import data.CurrentTask;
import data.SFile;
import data.SMethod;

/**
 * This class will normalize the file's source code by applying the appropriate
 * techniques to remove comments and special syntax that is not needed. The
 * language type of the file will dictate how these will be removed.
 * 
 * @author Kevin.J.Jalbert
 */
public class Cyclolizer implements Runnable{

	// Get instance of CurrentTask
	private static CurrentTask currentTask = CurrentTask.getInstance();
	public String threadName = null;
	private SFile currentFile = null;
	public final int BUFFER_SIZE = 127; // Buffer size to increase speed

	/**
	 * Instantiates a new cyclolizer.
	 * 
	 * @param threadName the thread name
	 */
	public Cyclolizer(String threadName) {
		this.threadName = threadName;
	}

	/**
	 * This thread will look for files on the cyclolizer queue in the
	 * currentTask object. If there is no files available it will have a timeout
	 * check to ensure that the queue is indeed empty. When a file is acquired
	 * the contents are read and the cyclolizer process occurs.
	 */
	@Override
	public void run() {
		int timeout = 10;

		// Check to make sure the queue isn't empty
		while (timeout != 0){

			// Have a thread delay to allow the queue to refill
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			// Check to make sure either queue isn't empty
			while(!currentTask.isCycloQueueEmpty() || !currentTask.isNormQueueEmpty()){
				timeout = 10; // Reset the timeout counter

				SFile readyFile = currentTask.getFileFromCycloQueue();

				// Check to make sure the File is valid
				if (readyFile != null){

					this.currentFile = readyFile;
					String code = currentFile.getCleanCode();
					currentFile.clearCleanCode(); // Clear the cleanCode field to save space
					readContent(code);
				}			
			}
			timeout--;
		}
	}

	/**
	 * Read content of the code and make use of java scanners to separate code
	 * into methods. The method signatures are also captured so that they the 
	 * metrics can be related back to a method.
	 * 
	 * @param code the working file
	 */
	private void readContent(String code){

		// TODO Have a check on the file type and use that language
		// TODO Maybe have a check to see if the language uses classes, if so then take that into account

		Scanner openScan = null;
		Scanner closeScan = null;
		String bodyPart = "";
		String body = "";
		String name = "";
		String lastName = "";
		int opened = 0;
		int nestedLevel = 0;
		boolean notDone = true;
		boolean foundMethod = false;

		// Create a scanner to section the code
		openScan = new Scanner(code);

		// TODO Use the language of the file to use the right delimiter for the method openings
		openScan.useDelimiter("[{]");

		// If there is a method available the grab the name, otherwise it is undefined
		if (openScan.hasNext()){
			lastName = openScan.next();			
		}
		else{
			lastName = "N/A";
		}

		// Keep looping till all the methods are acquired
		while (notDone){

			if(openScan.hasNext()){

				// Acquire the next part of the method and increment level of nestedness
				bodyPart = openScan.next();
				opened++;

				// Now take this section and make a new scanner out of it 
				closeScan = new Scanner(bodyPart);

				// TODO Use the language of the file to use the right delimiter for the method openings
				closeScan.useDelimiter("}");

				// Look to see if there is an ending delimiter
				if (closeScan.hasNext()){
					closeScan.next();

					// Check for closes in this body
					while(closeScan.hasNext()){

						if (opened > nestedLevel){
							nestedLevel = opened;
						}

						// A close delimiter was found so decrease the open
						opened--;

						// Check to see if the closes and opens cancel to indicate the method ended
						if (opened == 0){
							// Complete the body of the method
							body = body + "{" + bodyPart;

							// Find the name of the method
							name = body.substring(body.lastIndexOf("}") + 2);
							foundMethod = true;
							break;
						}
						else { // If the method isn't done then read the next block
							closeScan.next();
						}
					}
				}

				// If a method is found
				if (foundMethod){

					// Analyze this method
					doCyclo(lastName, body, nestedLevel);
					lastName = name;
					bodyPart = "";
					body = "";
					foundMethod = false;				
				}
				else{ // Method isn't found, then append to the body of the method
					body = body + "{" + bodyPart;
				}			
			}
			else{ // No more of the code to scan, break out of the loop
				notDone = false;
			}
		}

		// Note that the file is done
		currentTask.fileFinished(this.currentFile);
	}

	/**
	 * Do the actual cyclomatic complexity analysis on the method. Scanners are
	 * used to count the number of a control mechanisms. 
	 * 
	 * @param name the signature of the method
	 * @param method the method's body
	 * @param opened the level of nestedness for the method
	 */
	private void doCyclo(String name, String method, int opened) {

		boolean notDone = true;
		int nextControl = 0;
		Scanner scan = null;
		int counter = -1; // Counts the number of control mechanisms found
		int forCount = 0;
		int whileCount = 0;
		int ifCount = 0;
		int elseIfCount = 0;
		int switchCount = 0;
		int caseCount = 0;

		/*
		 * TODO Need to check for situations where there are no {
		 * 		if(true)
		 *  		dothis();
		 *  	//Code continues, no { was used
		 */

		scan = new Scanner(method);
		// Start by counting the number of for loops, using a regex as a delimiter
		scan.useDelimiter("[;|\\s|{|}|(|)]for[;|\\s|{|}|(|)]");

		// Keep looping till all the cases of searching for control mechanisms are done
		while (notDone){

			// If this method has a section that has been matched
			if(scan.hasNext()){
				scan.next();

				// If this is a switch or case control mechanism then just increment the count
				if(nextControl == 4 || nextControl == 5){
					counter++;
				}
				else{ // Otherwise need to check for compounded conditions

					String oldDelimiter = scan.delimiter().toString();
					//  TODO Checking for everything since {} in methods are not working correctly
					scan.useDelimiter("[{]|[}]|[;]");

					// Check to see if there is an ending delimeter
					if (scan.hasNext()){

						// Look for end of the compounding statement
						String compound = scan.next();

						// Count number of || and && by replacing them with one less character
						int before = compound.length();
						compound = compound.replace("||", "|");
						compound = compound.replace("&&", "&");
						int after = compound.length();

						// The difference will tell the number of compounds
						counter += (before-after) + 1;
					}
					else {
						// Add to the counter since something was found
						counter++;
					}

					// Return the scanner's delimiter back to the control mechanism being found
					scan.useDelimiter(oldDelimiter);
				}
			}
			else{ // When there are no more control mechanisms for that type

				// Increase control to the next case
				nextControl++;
				scan.close();

				// Make note of the counter, and make a new scanner for the next type
				switch (nextControl) {
				case 1: // Look for While control mechanisms
					forCount += counter;
					scan = new Scanner(method);
					scan.useDelimiter("[;|\\s|{|}|(|)]while[;|\\s|{|}|(|)]");
					break;
				case 2: // Look for If control mechanisms
					whileCount += counter;
					scan = new Scanner(method);
					scan.useDelimiter("[;|\\s|{|}|(|)]if[;|\\s|{|}|(|)]");
					break;
				case 3: // Look for Else If control mechanisms
					ifCount += counter;
					scan = new Scanner(method);
					scan.useDelimiter("[;|\\s|{|}|(|)]else if[;|\\s|{|}|(|)]");
					break;
				case 4: // Look for Switch control mechanisms
					elseIfCount += counter;
					scan = new Scanner(method);
					scan.useDelimiter("[;|\\s|{|}|(|)]switch[;|\\s|{|}|(|)]");
					break;
				case 5: // Look for Case control mechanisms
					switchCount += counter;
					scan = new Scanner(method);
					scan.useDelimiter("[;|\\s|{|}|(|)]case[;|\\s|{|}|(|)]");
					break;
				case 6: // Done looking for control mechanisms
					notDone = false;
					caseCount = counter;
				}
				counter = -1;
			}
		}

		// Acquire the source lines for this method
		int sourceLines = findSourceLines(method);

		// Trim the name and fix the if/else if count (duplications)
		name = name.replaceAll(";", "").trim();
		ifCount = ifCount - elseIfCount;

		// Make a method and add the information to the respected file
		SMethod sMethod = new SMethod(forCount, whileCount, ifCount, elseIfCount, switchCount, caseCount, sourceLines, opened, name);
		this.currentFile.addSMethod(sMethod);
	}

	/**
	 * Find out the number of source lines there are in this method.
	 * 
	 * @param method the method's body
	 * 
	 * @return sourceLines the number of source lines
	 */
	private int findSourceLines(String method) {

		int sourceLines = 0;

		// Read the method as a BufferedReader
		StringReader stringReader = null;
		stringReader = new StringReader(method);
		BufferedReader bufReader = new BufferedReader(stringReader);

		// If bufferReader is ready start parsing the sourceFile
		String curLine = "";
		try {
			if(bufReader.ready()){
				// For as long as there are lines left to read; acquire current one
				while((curLine = bufReader.readLine()) != null){
					sourceLines++; // Count the number of source lines
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return sourceLines;
	}
}